fix SchemaArrayField/SchemaArrayData mismatch (#1177)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Thu, 14 Sep 2023 12:54:38 +0000 (06:54 -0600)
committerGitHub <noreply@github.com>
Thu, 14 Sep 2023 12:54:38 +0000 (06:54 -0600)
* fix SchemaArrayField/SchemaArrayData mismatch

related to IGC additions from #1060 and #1054.

* retire global trait collection

* use class member to store track traits instead of fs data.

* fiddle with kml accumlate hash

* compute number of wp_fields

* correct bug concerning track specific traits.

* simplify multi-track array decisions.

* simplify kml multi-track schema decisions.

defs.h
kml.cc
kml.h
reference/realtime.kml
reference/track/92GV66G1.igc.kml
reference/track/92HV66G1.igc.kml
reference/track/gpx_garmin_extensions-kml_track.kml
reference/track/gtrnctr_power-kml.kml
route.cc
waypt.cc

diff --git a/defs.h b/defs.h
index 18fe9c0cfa50041c9377b8971cf9062b655e40c5..1ab8517999303915606d04773b3ac91d66c65e8a 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -248,30 +248,6 @@ public:
   unsigned int marked_for_deletion:1;          /* True if schedulded for deletion. */
 };
 
-// These are dicey as they're collected on read. Subsequent filters may change
-// things, though it's unlikely to matter in practical terms.  Don't use these
-// if a false positive would be deleterious.
-#
-class global_trait
-{
-public:
-  global_trait() :
-    trait_geocaches(0),
-    trait_heartrate(0),
-    trait_cadence(0),
-    trait_power(0),
-    trait_depth(0),
-    trait_temperature(0),
-    trait_sat(0) {}
-  unsigned int trait_geocaches:1;
-  unsigned int trait_heartrate:1;
-  unsigned int trait_cadence:1;
-  unsigned int trait_power:1;
-  unsigned int trait_depth:1;
-  unsigned int trait_temperature:1;
-  unsigned int trait_sat:1;
-};
-
 /*
  *  Bounding box information.
  */
@@ -509,7 +485,6 @@ public:
   using QList<Waypoint*>::size_type;
 };
 
-const global_trait* get_traits();
 void waypt_init();
 //void update_common_traits(const Waypoint* wpt);
 void waypt_add(Waypoint* wpt);
diff --git a/kml.cc b/kml.cc
index 159cde986b5a69b635b6ce5c0b8568b761dd1076..dbf3e28b298bce4e4d4cbef31367a94d5024e5d9 100644 (file)
--- a/kml.cc
+++ b/kml.cc
@@ -35,6 +35,7 @@
 #include <QDate>                       // for QDate
 #include <QDateTime>                   // for QDateTime
 #include <QFile>                       // for QFile
+#include <QHash>                       // for QHash
 #include <QIODevice>                   // for operator|, QIODevice, QIODevice::Text, QIODevice::WriteOnly
 #include <QList>                       // for QList
 #include <QString>                     // for QString, QStringLiteral, operator+, operator!=
 
 #define MYNAME "kml"
 
+const QVector<KmlFormat::mt_field_t> KmlFormat::mt_fields_def = {
+  { wp_field::igc_enl, "igc_enl", "Engine Noise", "double" },
+  { wp_field::igc_tas, "igc_tas", "True Airspd", "double" },
+  { wp_field::igc_oat, "igc_oat", "Otsd Air Temp", "double" },
+  { wp_field::igc_vat, "igc_vat", "Ttl Enrg Vario", "double" },
+  { wp_field::igc_gsp, "igc_gsp", "Ground Speed", "double" },
+  { wp_field::igc_fxa, "igc_fxa", "Fix Accuracy", "double" },
+  { wp_field::igc_gfo, "igc_gfo", "G Force?", "double" },
+  { wp_field::igc_acz, "igc_acz", "Z Accel", "double" },
+  { wp_field::igc_siu, "igc_siu", "# Of Sats", "double" },
+  { wp_field::igc_trt, "igc_trt", "True Track", "double" },
+  { wp_field::cadence, "cadence", "Cadence", "int" },
+  { wp_field::depth, "depth", "Depth", "float" },
+  { wp_field::heartrate, "heartrate", "Heart Rate", "int" },
+  { wp_field::temperature, "temperature", "Temperature", "float" },
+  { wp_field::power, "power", "Power", "float" },
+  { wp_field::sat, "satellites", "Satellites", "int" },
+};
+
 void KmlFormat::kml_init_color_sequencer(unsigned int steps_per_rev)
 {
   if (rotate_colors) {
@@ -101,7 +121,7 @@ void KmlFormat::kml_step_color()
   } else if ((color_seq >= (2*kml_color_limit)) && (color_seq < (3*kml_color_limit))) {
     kml_color_sequencer.color.bbggrr = kml_bgr_to_color(color_seq-2*kml_color_limit, kml_color_limit, 0);
   } else if ((color_seq >= (3*kml_color_limit)) && (color_seq < (4*kml_color_limit))) {
-    kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 4*kml_color_limit-color_seq ,0);
+    kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 4*kml_color_limit-color_seq,0);
   } else if ((color_seq >= (4*kml_color_limit)) && (color_seq < (5*kml_color_limit))) {
     kml_color_sequencer.color.bbggrr = kml_bgr_to_color(kml_color_limit, 0, color_seq-4*kml_color_limit);
   } else if ((color_seq >= (5*kml_color_limit)) && (color_seq < (6*kml_color_limit))) {
@@ -417,6 +437,9 @@ void KmlFormat::wr_deinit()
     QFile::remove(posnfilename);
     QFile::rename(posnfilenametmp, posnfilename);
   }
+
+  kml_track_traits.reset();
+  kml_track_traits_hash.clear();
 }
 
 void KmlFormat::wr_position_deinit()
@@ -424,6 +447,9 @@ void KmlFormat::wr_position_deinit()
 //     kml_wr_deinit();
   posnfilename.clear();
   posnfilenametmp.clear();
+
+  kml_track_traits.reset();
+  kml_track_traits_hash.clear();
 }
 
 
@@ -1463,13 +1489,13 @@ void KmlFormat::kml_track_tlr(const route_head* header)
  */
 
 void KmlFormat::kml_mt_simple_array(const route_head* header,
-                                    const char* name,
+                                    const QString& name,
                                     wp_field member) const
 {
   writer->writeStartElement(QStringLiteral("gx:SimpleArrayData"));
   writer->writeAttribute(QStringLiteral("name"), name);
   if (global_opts.debug_level >= 3) {
-    printf(MYNAME ": New KML SimpleArray: %s\n", name);
+    printf(MYNAME ": New KML SimpleArray: %s\n", qPrintable(name));
   }
   foreach (const Waypoint* wpt, header->waypoint_list) {
     const auto* fs_igc = reinterpret_cast<igc_fsdata*>(wpt->fs.FsChainFind(kFsIGC));
@@ -1496,7 +1522,7 @@ void KmlFormat::kml_mt_simple_array(const route_head* header,
       break;
     case wp_field::sat:
       writer->writeTextElement(QStringLiteral("gx:value"), wpt->sat >= 0?
-                                QString::number(wpt->sat) : QString());
+                               QString::number(wpt->sat) : QString());
       break;
     case wp_field::igc_enl:
     case wp_field::igc_tas:
@@ -1511,14 +1537,14 @@ void KmlFormat::kml_mt_simple_array(const route_head* header,
       if (fs_igc && fs_igc->get_value(member).has_value()) {
         double value = fs_igc->get_value(member).value();
         if (global_opts.debug_level >= 6) {
-          printf(MYNAME ": Writing KML SimpleArray data: %s of %f\n", name, value);
+          printf(MYNAME ": Writing KML SimpleArray data: %s of %f\n", qPrintable(name), value);
         }
         writer->writeTextElement(QStringLiteral("gx:value"), QString::number(value));
-      // No igc_fsdata present, but we still need to write out the SimpleArray.
-      // This can happen when merging tracks with different sets of IGC extensions.
+        // No igc_fsdata present, but we still need to write out the SimpleArray.
+        // This can happen when merging tracks with different sets of IGC extensions.
       } else {
         if (global_opts.debug_level >= 7) {
-          printf(MYNAME ": Writing empty KML SimpleArray data for %s\n", name);
+          printf(MYNAME ": Writing empty KML SimpleArray data for %s\n", qPrintable(name));
         }
         writer->writeTextElement(QStringLiteral("gx:value"), QString());
       }
@@ -1556,200 +1582,141 @@ void KmlFormat::write_as_linestring(const route_head* header)
 
 }
 
-void KmlFormat::kml_mt_hdr(const route_head* header)
+void KmlFormat::kml_accumulate_track_traits(const route_head* rte)
 {
-  bool has_cadence = false;
-  bool has_depth = false;
-  bool has_heartrate = false;
-  bool has_temperature = false;
-  bool has_power = false;
-  bool has_sat = false;
-  bool has_igc_exts = false;
-  bool has_igc_enl = false;
-  bool has_igc_tas = false;
-  bool has_igc_oat = false;
-  bool has_igc_vat = false;
-  bool has_igc_gsp = false;
-  bool has_igc_fxa = false;
-  bool has_igc_gfo = false;
-  bool has_igc_acz = false;
-  bool has_igc_siu = false; // Not very useful to graph
-  bool has_igc_trt = false; // Not very useful to graph
-
-  // This logic is kind of inside-out for GPSBabel.  If a track doesn't
-  // have enough interesting timestamps, just write it as a LineString.
-  if (!track_has_time(header)) {
-    write_as_linestring(header);
-    return;
-  }
-
-  writer->writeStartElement(QStringLiteral("Placemark"));
-  writer->writeOptionalTextElement(QStringLiteral("name"), header->rte_name);
-  writer->writeTextElement(QStringLiteral("styleUrl"), QStringLiteral("#multiTrack"));
-  writer->writeStartElement(QStringLiteral("gx:Track"));
-  kml_output_positioning(false);
-
-  foreach (const Waypoint* tpt, header->waypoint_list) {
-
-    if (tpt->GetCreationTime().isValid()) {
-      QString time_string = tpt->CreationTimeXML();
-      writer->writeOptionalTextElement(QStringLiteral("when"), time_string);
-    } else {
-      writer->writeStartElement(QStringLiteral("when"));
-      writer->writeEndElement(); // Close when tag
-    }
-  }
-
-  // TODO: How to handle clamped, floating, extruded, etc.?
-  foreach (const Waypoint* tpt, header->waypoint_list) {
+  track_trait_t track_traits;
 
+  foreach (const Waypoint* tpt, rte->waypoint_list) {
     const auto* fs_igc = reinterpret_cast<igc_fsdata*>(tpt->fs.FsChainFind(kFsIGC));
-    if (kml_altitude_known(tpt)) {
-      writer->writeTextElement(QStringLiteral("gx:coord"),
-                               QString::number(tpt->longitude, 'f', precision) + QString(" ") +
-                               QString::number(tpt->latitude, 'f', precision) + QString(" ") +
-                               QString::number(tpt->altitude, 'f', 2)
-                              );
-    } else {
-      writer->writeTextElement(QStringLiteral("gx:coord"),
-                               QString::number(tpt->longitude, 'f', precision) + QString(" ") +
-                               QString::number(tpt->latitude, 'f', precision)
-                              );
-    }
 
     // Capture interesting traits to see if we need to do an ExtendedData
     // section later.
     if (tpt->cadence) {
-      has_cadence = true;
+      track_traits[static_cast<int>(wp_field::cadence)] = true;
     }
     if (tpt->depth_has_value()) {
-      has_depth = true;
+      track_traits[static_cast<int>(wp_field::depth)] = true;
     }
     if (tpt->heartrate) {
-      has_heartrate = true;
+      track_traits[static_cast<int>(wp_field::heartrate)] = true;
     }
     if (tpt->temperature_has_value()) {
-      has_temperature = true;
+      track_traits[static_cast<int>(wp_field::temperature)] = true;
     }
     if (tpt->power) {
-      has_power = true;
+      track_traits[static_cast<int>(wp_field::power)] = true;
     }
     // # of satellites can legitimately be zero, so -1 means no data in this case
     if (tpt->sat >= 0) {
-      has_sat = true;
+      track_traits[static_cast<int>(wp_field::sat)] = true;
     }
     if (fs_igc) {
-      has_igc_exts = true;
       if (fs_igc->enl.has_value()) {
-        has_igc_enl = true;
+        track_traits[static_cast<int>(wp_field::igc_enl)] = true;
       }
       if (fs_igc->tas.has_value()) {
-        has_igc_tas = true;
+        track_traits[static_cast<int>(wp_field::igc_tas)] = true;
       }
       if (fs_igc->oat.has_value()) {
-        has_igc_oat = true;
+        track_traits[static_cast<int>(wp_field::igc_oat)] = true;
       }
       if (fs_igc->vat.has_value()) {
-        has_igc_vat = true;
+        track_traits[static_cast<int>(wp_field::igc_vat)] = true;
       }
       if (fs_igc->gsp.has_value()) {
-        has_igc_gsp = true;
+        track_traits[static_cast<int>(wp_field::igc_gsp)] = true;
       }
       if (fs_igc->fxa.has_value()) {
-        has_igc_fxa = true;
+        track_traits[static_cast<int>(wp_field::igc_fxa)] = true;
       }
       if (fs_igc->gfo.has_value()) {
-        has_igc_gfo = true;
+        track_traits[static_cast<int>(wp_field::igc_gfo)] = true;
       }
       if (fs_igc->acz.has_value()) {
-        has_igc_acz = true;
+        track_traits[static_cast<int>(wp_field::igc_acz)] = true;
       }
       if constexpr(kIncludeIGCSIU) {
         if (fs_igc->siu.has_value()) {
-          has_igc_siu = true;
+          track_traits[static_cast<int>(wp_field::igc_siu)] = true;
         }
       }
       if constexpr(kIncludeIGCTRT) {
         if (fs_igc->trt.has_value()) {
-          has_igc_trt = true;
+          track_traits[static_cast<int>(wp_field::igc_trt)] = true;
         }
       }
     }
   }
 
-  // This gets unwieldly if we check each individual igc extension,
-  // hence the has_igc_exts flag.
-  if (has_cadence || has_depth || has_heartrate || has_temperature ||
-      has_power || has_sat || has_igc_exts) {
-    bool include_kmt_sats = true;
-    bool include_kmt_temperature = true;
-    writer->writeStartElement(QStringLiteral("ExtendedData"));
-    writer->writeStartElement(QStringLiteral("SchemaData"));
-    writer->writeAttribute(QStringLiteral("schemaUrl"), QStringLiteral("#schema"));
+  // For dual source fields give priority to igc.
+  if (track_traits[static_cast<int>(wp_field::igc_oat)]) {
+    track_traits[static_cast<int>(wp_field::temperature)] = false;
+  }
+  if (track_traits[static_cast<int>(wp_field::igc_siu)]) {
+    track_traits[static_cast<int>(wp_field::sat)] = false;
+  }
 
-    // Perhaps not the /best/ way to do this, but this if ladder
-    // should only be evaluated once.
-    if (has_igc_exts) {
-      if (has_igc_enl) {
-        kml_mt_simple_array(header, kmt_igc_enl, wp_field::igc_enl);
-      }
-      if (has_igc_tas) {
-        kml_mt_simple_array(header, kmt_igc_tas, wp_field::igc_tas);
-      }
-      if (has_igc_oat) {
-        kml_mt_simple_array(header, kmt_igc_oat, wp_field::igc_oat);
-        include_kmt_temperature = false;
-      }
-      if (has_igc_vat) {
-        kml_mt_simple_array(header, kmt_igc_vat, wp_field::igc_vat);
-      }
-      if (has_igc_gsp) {
-        kml_mt_simple_array(header, kmt_igc_gsp, wp_field::igc_gsp);
-      }
-      if (has_igc_fxa) {
-        kml_mt_simple_array(header, kmt_igc_fxa, wp_field::igc_fxa);
-      }
-      if (has_igc_gfo) {
-        kml_mt_simple_array(header, kmt_igc_gfo, wp_field::igc_gfo);
-      }
-      if (has_igc_acz) {
-        kml_mt_simple_array(header, kmt_igc_acz, wp_field::igc_acz);
-      }
-      if constexpr(kIncludeIGCSIU) {
-        if (has_igc_siu) {
-          kml_mt_simple_array(header, kmt_igc_siu, wp_field::igc_siu);
-          include_kmt_sats = false;
-        }
-      }
-      if constexpr(kIncludeIGCTRT) {
-        if (has_igc_trt) {
-          kml_mt_simple_array(header, kmt_igc_trt, wp_field::igc_trt);
-        }
-      }
-    }
+  // When doing real time positioning we may already have been here.
+  // If so, replace the previous value corresponding to the key.
+  // If not, insert a new key value pair.
+  kml_track_traits_hash.insert(rte, track_traits);
+  kml_track_traits |= track_traits;
+}
 
-    if (has_cadence) {
-      kml_mt_simple_array(header, kmt_cadence, wp_field::cadence);
-    }
+void KmlFormat::kml_mt_hdr(const route_head* header)
+{
+  // This logic is kind of inside-out for GPSBabel.  If a track doesn't
+  // have enough interesting timestamps, just write it as a LineString.
+  if (!track_has_time(header)) {
+    write_as_linestring(header);
+    return;
+  }
 
-    if (has_depth) {
-      kml_mt_simple_array(header, kmt_depth, wp_field::depth);
-    }
+  writer->writeStartElement(QStringLiteral("Placemark"));
+  writer->writeOptionalTextElement(QStringLiteral("name"), header->rte_name);
+  writer->writeTextElement(QStringLiteral("styleUrl"), QStringLiteral("#multiTrack"));
+  writer->writeStartElement(QStringLiteral("gx:Track"));
+  kml_output_positioning(false);
 
-    if (has_heartrate) {
-      kml_mt_simple_array(header, kmt_heartrate, wp_field::heartrate);
-    }
+  foreach (const Waypoint* tpt, header->waypoint_list) {
 
-    if (has_temperature && include_kmt_temperature) {
-      kml_mt_simple_array(header, kmt_temperature, wp_field::temperature);
+    if (tpt->GetCreationTime().isValid()) {
+      QString time_string = tpt->CreationTimeXML();
+      writer->writeOptionalTextElement(QStringLiteral("when"), time_string);
+    } else {
+      writer->writeStartElement(QStringLiteral("when"));
+      writer->writeEndElement(); // Close when tag
     }
+  }
 
-    if (has_power) {
-      kml_mt_simple_array(header, kmt_power, wp_field::power);
+  // TODO: How to handle clamped, floating, extruded, etc.?
+  foreach (const Waypoint* tpt, header->waypoint_list) {
+
+    if (kml_altitude_known(tpt)) {
+      writer->writeTextElement(QStringLiteral("gx:coord"),
+                               QString::number(tpt->longitude, 'f', precision) + QString(" ") +
+                               QString::number(tpt->latitude, 'f', precision) + QString(" ") +
+                               QString::number(tpt->altitude, 'f', 2)
+                              );
+    } else {
+      writer->writeTextElement(QStringLiteral("gx:coord"),
+                               QString::number(tpt->longitude, 'f', precision) + QString(" ") +
+                               QString::number(tpt->latitude, 'f', precision)
+                              );
     }
+  }
+
+
+  auto track_traits = kml_track_traits_hash.value(header);
+  if (track_traits.any()) {
+    writer->writeStartElement(QStringLiteral("ExtendedData"));
+    writer->writeStartElement(QStringLiteral("SchemaData"));
+    writer->writeAttribute(QStringLiteral("schemaUrl"), QStringLiteral("#schema"));
 
-    if (has_sat && include_kmt_sats) {
-      kml_mt_simple_array(header, kmt_sat, wp_field::sat);
+    for (const auto& flddef : mt_fields_def) {
+      if (track_traits[static_cast<int>(flddef.id)]) {
+        kml_mt_simple_array(header, flddef.name, flddef.id);
+      }
     }
 
     writer->writeEndElement(); // Close SchemaData tag
@@ -1757,7 +1724,6 @@ void KmlFormat::kml_mt_hdr(const route_head* header)
   }
 }
 
-
 void KmlFormat::kml_mt_tlr(const route_head* header) const
 {
   if (track_has_time(header)) {
@@ -1850,8 +1816,8 @@ void KmlFormat::kml_write_AbstractView()
 }
 
 
-void KmlFormat::kml_mt_array_schema(const char* field_name, const char* display_name,
-                                    const char* type) const
+void KmlFormat::kml_mt_array_schema(const QString& field_name, const QString& display_name,
+                                    const QString& type) const
 {
   writer->writeStartElement(QStringLiteral("gx:SimpleArrayField"));
   writer->writeAttribute(QStringLiteral("name"), field_name);
@@ -1862,8 +1828,6 @@ void KmlFormat::kml_mt_array_schema(const char* field_name, const char* display_
 
 void KmlFormat::write()
 {
-  const global_trait* traits = get_traits();
-
   // Parse options
   export_lines = (0 == strcmp("1", opt_export_lines));
   export_points = (0 == strcmp("1", opt_export_points));
@@ -1927,38 +1891,35 @@ void KmlFormat::write()
     writer->writeEndElement(); // Close Style tag
   }
 
-  if (traits->trait_geocaches) {
+  bool has_geocaches = false;
+  auto kml_accumulate_wpt_traits_lambda = [&has_geocaches](const Waypoint* wpt)->void {
+    has_geocaches |= (wpt->gc_data->diff && wpt->gc_data->terr);
+  };
+  waypt_disp_all(kml_accumulate_wpt_traits_lambda);
+
+  if (has_geocaches) {
     kml_gc_make_balloonstyle();
   }
 
-  if (traits->trait_heartrate ||
-      traits->trait_cadence ||
-      traits->trait_power ||
-      traits->trait_temperature ||
-      traits->trait_depth ||
-      traits->trait_sat) {
-    writer->writeStartElement(QStringLiteral("Schema"));
-    writer->writeAttribute(QStringLiteral("id"), QStringLiteral("schema"));
+  if (export_track) {
+    kml_track_traits.reset();
+    kml_track_traits_hash.clear();
+    auto kml_accumulate_track_traits_lambda = [this](const route_head* rte)->void {
+      kml_accumulate_track_traits(rte);
+    };
+    track_disp_all(kml_accumulate_track_traits_lambda, nullptr, nullptr);
 
-    if (traits->trait_heartrate) {
-      kml_mt_array_schema(kmt_heartrate, "Heart Rate", "int");
-    }
-    if (traits->trait_cadence) {
-      kml_mt_array_schema(kmt_cadence, "Cadence", "int");
-    }
-    if (traits->trait_power) {
-      kml_mt_array_schema(kmt_power, "Power", "float");
-    }
-    if (traits->trait_temperature) {
-      kml_mt_array_schema(kmt_temperature, "Temperature", "float");
-    }
-    if (traits->trait_depth) {
-      kml_mt_array_schema(kmt_depth, "Depth", "float");
-    }
-    if (traits->trait_sat) {
-      kml_mt_array_schema(kmt_sat, "Satellites", "int");
+    if (kml_track_traits.any()) {
+      writer->writeStartElement(QStringLiteral("Schema"));
+      writer->writeAttribute(QStringLiteral("id"), QStringLiteral("schema"));
+
+      for (const auto& flddef : mt_fields_def) {
+        if (kml_track_traits[static_cast<int>(flddef.id)]) {
+          kml_mt_array_schema(flddef.name, flddef.displayName, flddef.type);
+        }
+      }
+      writer->writeEndElement(); // Close Schema tag
     }
-    writer->writeEndElement(); // Close Schema tag
   }
 
   if (waypt_count()) {
diff --git a/kml.h b/kml.h
index 505e7aeac1c79f743262581e3d1b9cf43493a8ce..d51f190dc5592b9feb99a22e0a12e027904243c3 100644 (file)
--- a/kml.h
+++ b/kml.h
 #ifndef KML_H_INCLUDED_
 #define KML_H_INCLUDED_
 
+#include <bitset>                       // for bitset
 #include <tuple>                        // for tuple, make_tuple, tie
 
+#include <QHash>                        // for QHash
 #include <QList>                        // for QList
 #include <QString>                      // for QString, QStringLiteral, operator+, operator!=
 #include <QVector>                      // for QVector
@@ -69,7 +71,7 @@ public:
 // Helper to write gx:SimpleList, iterating over a route queue and writing out.
 
   enum class wp_field {
-    cadence,
+    cadence = 0,
     depth,
     heartrate,
     temperature,
@@ -86,6 +88,7 @@ public:
     igc_siu,  // Satellites In Use
     igc_acz   // Z Acceleration
   };
+  static constexpr int number_wp_fields = static_cast<int>(wp_field::igc_acz) + 1;
 
 private:
   /* Types */
@@ -99,6 +102,15 @@ private:
     kmlpt_other
   };
 
+  struct mt_field_t {
+    wp_field id;
+    const QString name;
+    const QString displayName;
+    const QString type;
+  };
+
+  using track_trait_t = std::bitset<number_wp_fields>;
+
   /* Constants */
   static constexpr const char* default_precision = "6";
   static constexpr int kml_color_limit = 204;  /* allowed range [0,255] */
@@ -118,25 +130,6 @@ private:
     nullptr
   };
 
-  // Multitrack ids to correlate Schema to SchemaData
-  static constexpr const char* kmt_heartrate = "heartrate";
-  static constexpr const char* kmt_cadence = "cadence";
-  static constexpr const char* kmt_temperature = "temperature";
-  static constexpr const char* kmt_depth = "depth";
-  static constexpr const char* kmt_power = "power";
-  static constexpr const char* kmt_sat = "satellites";
-  // Constants pertaining to IGC files would be better defined in either igc.h or formspec.h
-  static constexpr const char* kmt_igc_enl = "Engine Noise";
-  static constexpr const char* kmt_igc_vat = "Ttl Enrg Vario";
-  static constexpr const char* kmt_igc_tas = "True Airspd";
-  static constexpr const char* kmt_igc_oat = "Otsd Air Temp";
-  static constexpr const char* kmt_igc_trt = "True Track";
-  static constexpr const char* kmt_igc_gsp = "Ground Speed";
-  static constexpr const char* kmt_igc_fxa = "Fix Accuracy";
-  static constexpr const char* kmt_igc_gfo = "G Force?";
-  static constexpr const char* kmt_igc_siu = "# Of Sats";
-  static constexpr const char* kmt_igc_acz = "Z Accel";
-
   // IGC option compile-time flags
   static constexpr bool kIncludeIGCSIU = true;
   static constexpr bool kIncludeIGCTRT = false;
@@ -196,20 +189,25 @@ private:
   void kml_track_hdr(const route_head* header) const;
   void kml_track_disp(const Waypoint* waypointp) const;
   void kml_track_tlr(const route_head* header);
-  void kml_mt_simple_array(const route_head* header, const char* name, wp_field member) const;
+  void kml_mt_simple_array(const route_head* header, const QString& name, wp_field member) const;
   static bool track_has_time(const route_head* header);
   void write_as_linestring(const route_head* header);
+  void kml_accumulate_track_traits(const route_head* rte);
   void kml_mt_hdr(const route_head* header);
   void kml_mt_tlr(const route_head* header) const;
   void kml_route_hdr(const route_head* header) const;
   void kml_route_disp(const Waypoint* waypointp) const;
   void kml_route_tlr(const route_head* header);
   void kml_write_AbstractView();
-  void kml_mt_array_schema(const char* field_name, const char* display_name, const char* type) const;
+  void kml_mt_array_schema(const QString& field_name, const QString& display_name, const QString& type) const;
   static QString kml_get_posn_icon(int freshness);
 
   /* Data Members */
 
+  static const QVector<mt_field_t> mt_fields_def;
+  track_trait_t kml_track_traits;
+  QHash<const route_head*, track_trait_t> kml_track_traits_hash;
+
   // options
   char* opt_deficon{nullptr};
   char* opt_export_lines{nullptr};
index 11a9142490ae2068a6346dd4c081d8da9e24e0de..4524ae74d9c480b8199a0a82df892ef6a6c45d0b 100644 (file)
       </LineStyle>
     </Style>
     <Schema id="schema">
-      <gx:SimpleArrayField name="heartrate" type="int">
-        <displayName>Heart Rate</displayName>
-      </gx:SimpleArrayField>
       <gx:SimpleArrayField name="cadence" type="int">
         <displayName>Cadence</displayName>
       </gx:SimpleArrayField>
-      <gx:SimpleArrayField name="power" type="float">
-        <displayName>Power</displayName>
+      <gx:SimpleArrayField name="depth" type="float">
+        <displayName>Depth</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="heartrate" type="int">
+        <displayName>Heart Rate</displayName>
       </gx:SimpleArrayField>
       <gx:SimpleArrayField name="temperature" type="float">
         <displayName>Temperature</displayName>
       </gx:SimpleArrayField>
-      <gx:SimpleArrayField name="depth" type="float">
-        <displayName>Depth</displayName>
+      <gx:SimpleArrayField name="power" type="float">
+        <displayName>Power</displayName>
       </gx:SimpleArrayField>
       <gx:SimpleArrayField name="satellites" type="int">
         <displayName>Satellites</displayName>
index 046376dfb4f1fc05653315e12ce637ce7dfc5dba..390afe66101a2bb8e4a277e250c91571a392d6d0 100644 (file)
       </LineStyle>
     </Style>
     <Schema id="schema">
-      <gx:SimpleArrayField name="temperature" type="float">
-        <displayName>Temperature</displayName>
+      <gx:SimpleArrayField name="igc_enl" type="double">
+        <displayName>Engine Noise</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_tas" type="double">
+        <displayName>True Airspd</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_oat" type="double">
+        <displayName>Otsd Air Temp</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_vat" type="double">
+        <displayName>Ttl Enrg Vario</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_gsp" type="double">
+        <displayName>Ground Speed</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_fxa" type="double">
+        <displayName>Fix Accuracy</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_acz" type="double">
+        <displayName>Z Accel</displayName>
       </gx:SimpleArrayField>
     </Schema>
     <Folder>
           <gx:coord>-81.838083 28.408050 51.00</gx:coord>
           <ExtendedData>
             <SchemaData schemaUrl="#schema">
-              <gx:SimpleArrayData name="Engine Noise">
+              <gx:SimpleArrayData name="igc_enl">
                 <gx:value>31</gx:value>
                 <gx:value>1</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>15</gx:value>
                 <gx:value>4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="True Airspd">
+              <gx:SimpleArrayData name="igc_tas">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Otsd Air Temp">
+              <gx:SimpleArrayData name="igc_oat">
                 <gx:value>26.1</gx:value>
                 <gx:value>26.1</gx:value>
                 <gx:value>26.1</gx:value>
                 <gx:value>22.6</gx:value>
                 <gx:value>22.6</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ttl Enrg Vario">
+              <gx:SimpleArrayData name="igc_vat">
                 <gx:value>1.1</gx:value>
                 <gx:value>0.7</gx:value>
                 <gx:value>0.3</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>-0.1</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ground Speed">
+              <gx:SimpleArrayData name="igc_gsp">
                 <gx:value>0.31</gx:value>
                 <gx:value>0.08</gx:value>
                 <gx:value>0.05</gx:value>
                 <gx:value>0.02</gx:value>
                 <gx:value>0.01</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Fix Accuracy">
+              <gx:SimpleArrayData name="igc_fxa">
                 <gx:value>7</gx:value>
                 <gx:value>6</gx:value>
                 <gx:value>7</gx:value>
                 <gx:value>7</gx:value>
                 <gx:value>7</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Z Accel">
+              <gx:SimpleArrayData name="igc_acz">
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
           <gx:coord>-81.838083 28.408050 28.00</gx:coord>
           <ExtendedData>
             <SchemaData schemaUrl="#schema">
-              <gx:SimpleArrayData name="Engine Noise">
+              <gx:SimpleArrayData name="igc_enl">
                 <gx:value>31</gx:value>
                 <gx:value>1</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>15</gx:value>
                 <gx:value>4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="True Airspd">
+              <gx:SimpleArrayData name="igc_tas">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Otsd Air Temp">
+              <gx:SimpleArrayData name="igc_oat">
                 <gx:value>26.1</gx:value>
                 <gx:value>26.1</gx:value>
                 <gx:value>26.1</gx:value>
                 <gx:value>22.6</gx:value>
                 <gx:value>22.6</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ttl Enrg Vario">
+              <gx:SimpleArrayData name="igc_vat">
                 <gx:value>1.1</gx:value>
                 <gx:value>0.7</gx:value>
                 <gx:value>0.3</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>-0.1</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ground Speed">
+              <gx:SimpleArrayData name="igc_gsp">
                 <gx:value>0.31</gx:value>
                 <gx:value>0.08</gx:value>
                 <gx:value>0.05</gx:value>
                 <gx:value>0.02</gx:value>
                 <gx:value>0.01</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Fix Accuracy">
+              <gx:SimpleArrayData name="igc_fxa">
                 <gx:value>7</gx:value>
                 <gx:value>6</gx:value>
                 <gx:value>7</gx:value>
                 <gx:value>7</gx:value>
                 <gx:value>7</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Z Accel">
+              <gx:SimpleArrayData name="igc_acz">
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
index e5957a366b958dd6921aa7dfdbc3c251f0956e53..7975e47aae4a48d7967ca05e8cae2fc2c7ad9572 100644 (file)
       </LineStyle>
     </Style>
     <Schema id="schema">
-      <gx:SimpleArrayField name="temperature" type="float">
-        <displayName>Temperature</displayName>
+      <gx:SimpleArrayField name="igc_enl" type="double">
+        <displayName>Engine Noise</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_tas" type="double">
+        <displayName>True Airspd</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_oat" type="double">
+        <displayName>Otsd Air Temp</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_vat" type="double">
+        <displayName>Ttl Enrg Vario</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_gsp" type="double">
+        <displayName>Ground Speed</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_fxa" type="double">
+        <displayName>Fix Accuracy</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="igc_acz" type="double">
+        <displayName>Z Accel</displayName>
       </gx:SimpleArrayField>
     </Schema>
     <Folder>
           <gx:coord>-81.837917 28.408033 29.00</gx:coord>
           <ExtendedData>
             <SchemaData schemaUrl="#schema">
-              <gx:SimpleArrayData name="Engine Noise">
+              <gx:SimpleArrayData name="igc_enl">
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="True Airspd">
+              <gx:SimpleArrayData name="igc_tas">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Otsd Air Temp">
+              <gx:SimpleArrayData name="igc_oat">
                 <gx:value>28</gx:value>
                 <gx:value>28</gx:value>
                 <gx:value>28</gx:value>
                 <gx:value>25.4</gx:value>
                 <gx:value>25.4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ttl Enrg Vario">
+              <gx:SimpleArrayData name="igc_vat">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0.1</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ground Speed">
+              <gx:SimpleArrayData name="igc_gsp">
                 <gx:value>0.19</gx:value>
                 <gx:value>0.05</gx:value>
                 <gx:value>0.03</gx:value>
                 <gx:value>0.02</gx:value>
                 <gx:value>0.02</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Fix Accuracy">
+              <gx:SimpleArrayData name="igc_fxa">
                 <gx:value>8</gx:value>
                 <gx:value>8</gx:value>
                 <gx:value>8</gx:value>
                 <gx:value>5</gx:value>
                 <gx:value>5</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Z Accel">
+              <gx:SimpleArrayData name="igc_acz">
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
           <gx:coord>-81.837917 28.408033 35.00</gx:coord>
           <ExtendedData>
             <SchemaData schemaUrl="#schema">
-              <gx:SimpleArrayData name="Engine Noise">
+              <gx:SimpleArrayData name="igc_enl">
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
                 <gx:value>4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="True Airspd">
+              <gx:SimpleArrayData name="igc_tas">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Otsd Air Temp">
+              <gx:SimpleArrayData name="igc_oat">
                 <gx:value>28</gx:value>
                 <gx:value>28</gx:value>
                 <gx:value>28</gx:value>
                 <gx:value>25.4</gx:value>
                 <gx:value>25.4</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ttl Enrg Vario">
+              <gx:SimpleArrayData name="igc_vat">
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0</gx:value>
                 <gx:value>0.1</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Ground Speed">
+              <gx:SimpleArrayData name="igc_gsp">
                 <gx:value>0.19</gx:value>
                 <gx:value>0.05</gx:value>
                 <gx:value>0.03</gx:value>
                 <gx:value>0.02</gx:value>
                 <gx:value>0.02</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Fix Accuracy">
+              <gx:SimpleArrayData name="igc_fxa">
                 <gx:value>8</gx:value>
                 <gx:value>8</gx:value>
                 <gx:value>8</gx:value>
                 <gx:value>5</gx:value>
                 <gx:value>5</gx:value>
               </gx:SimpleArrayData>
-              <gx:SimpleArrayData name="Z Accel">
+              <gx:SimpleArrayData name="igc_acz">
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
                 <gx:value>9</gx:value>
index 81236455da88830bbf97e1c622e072980048375d..d4f43574f5e0e196c2c8ec93f3357aba4c48ade2 100644 (file)
       </LineStyle>
     </Style>
     <Schema id="schema">
-      <gx:SimpleArrayField name="heartrate" type="int">
-        <displayName>Heart Rate</displayName>
-      </gx:SimpleArrayField>
       <gx:SimpleArrayField name="cadence" type="int">
         <displayName>Cadence</displayName>
       </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="heartrate" type="int">
+        <displayName>Heart Rate</displayName>
+      </gx:SimpleArrayField>
     </Schema>
     <Folder>
       <name>Waypoints</name>
index 8c3b11c529012bd20259fe99b6d5b732d8a9bba2..dea07b9d85b0b84709a09aae52e6f243e03ce794 100644 (file)
       </LineStyle>
     </Style>
     <Schema id="schema">
-      <gx:SimpleArrayField name="heartrate" type="int">
-        <displayName>Heart Rate</displayName>
-      </gx:SimpleArrayField>
       <gx:SimpleArrayField name="cadence" type="int">
         <displayName>Cadence</displayName>
       </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="heartrate" type="int">
+        <displayName>Heart Rate</displayName>
+      </gx:SimpleArrayField>
       <gx:SimpleArrayField name="power" type="float">
         <displayName>Power</displayName>
       </gx:SimpleArrayField>
index a83e9dd886f638134e5b75909e03ef16d3de50fe..f0a17cc6e9d1b9453f79fc02438a3856f68be64e 100644 (file)
--- a/route.cc
+++ b/route.cc
@@ -38,8 +38,6 @@
 RouteList* global_route_list;
 RouteList* global_track_list;
 
-extern void update_common_traits(const Waypoint* wpt);
-
 void
 route_init()
 {
@@ -446,9 +444,6 @@ RouteList::add_wpt(route_head* rte, Waypoint* wpt, bool synth, QStringView namep
 {
   ++waypt_ct;
   rte->waypoint_list.add_rte_waypt(waypt_ct, wpt, synth, namepart, number_digits);
-  if ((this == global_route_list) || (this == global_track_list)) {
-    update_common_traits(wpt);
-  }
 }
 
 void
index e7f45e08cefe9108295c3f850c5a1778f18365af..8130add755c97b80d589c95a03cdf8b6518df3aa 100644 (file)
--- a/waypt.cc
+++ b/waypt.cc
 WaypointList* global_waypoint_list;
 
 Geocache Waypoint::empty_gc_data;
-static global_trait traits;
-
-const global_trait* get_traits()
-{
-  return &traits;
-}
 
 void
 waypt_init()
@@ -60,23 +54,6 @@ waypt_init()
   global_waypoint_list = new WaypointList;
 }
 
-void update_common_traits(const Waypoint* wpt)
-{
-  /* This is a bit tacky, but it allows a hint whether we've seen
-   * this data or not in the life cycle of this run.   Of course,
-   * the caches could have been filtered out of existence and not
-   * all waypoints may have this and a few other pitfalls, but it's
-   * an easy and fast test here.
-   */
-  traits.trait_geocaches |= (wpt->gc_data->diff && wpt->gc_data->terr);
-  traits.trait_heartrate |= wpt->heartrate > 0;
-  traits.trait_cadence |= wpt->cadence > 0;
-  traits.trait_power |= wpt->power > 0;
-  traits.trait_depth |= wpt->depth_has_value();
-  traits.trait_temperature |= wpt->temperature_has_value();
-  traits.trait_sat |= wpt->sat >= 0;
-}
-
 void
 waypt_add(Waypoint* wpt)
 {
@@ -637,10 +614,6 @@ WaypointList::waypt_add(Waypoint* wpt)
     }
   }
 
-  if (this == global_waypoint_list) {
-    update_common_traits(wpt);
-  }
-
 }
 
 void